package io.gitee.devaper.starlight.module.room.listener

import io.gitee.devaper.starlight.common.entity.po.*
import io.gitee.devaper.starlight.module.room.*
import io.gitee.devaper.starlight.module.room.constant.*
import io.gitee.devaper.starlight.security.event.*
import io.gitee.devaper.starlight.security.util.*
import org.slf4j.*
import org.springframework.context.ApplicationListener
import org.springframework.stereotype.Component
import org.springframework.util.Assert
import org.springframework.web.socket.WebSocketSession

/**
 * 并发会话强制登出事件监听器
 *
 * 用来自动踢出并发会话下被登出的用户WebSocket连接
 */
@Component
open class LogoutEventListener(
    private val roomRepository: RoomRepository
) : ApplicationListener<AbstractLogoutEvent> {

    override fun onApplicationEvent(event: AbstractLogoutEvent) {
        try {
            val user = tryAcquireUser(event.principal)

            Assert.isInstanceOf(UserPO::class.java, user)

            user!!
            // 这里将集合提出来, 不占用锁进行关闭连接
            // 原因: 这里使用到了room的读锁, 关闭客户端后会在同一条线程使用写锁, 从而造成死锁(如果这里改成写锁虽然可以重入, 但是对性能有影响)
            val candidates = ArrayList<WebSocketSession>(1)
            roomRepository.read {
                // 在所有房间内寻找用户, 一定是所有房间, 避免出现用户同时进入多个房间的情况
                roomRepository.map.values.forEach eachRoom@{ room ->
                    room.read {
                        if (room.clients.isNotEmpty()) {
                            // 在房间内查找
                            room.clients.forEach { client ->
                                if (client.principal.tryAcquireUser()?.id == user.id) {
                                    candidates.add(client)
                                    return@eachRoom
                                }
                            }
                        }
                    }
                }
            }

            // 关闭客户端连接
            candidates.forEach { client ->
                if (client.isOpen) {
                    try {
                        client.close(WS_AUTHENTICATION_FAILURE)
                    } catch (ignored: Exception) {
                    }
                }
            }
        } catch (e: Exception) {
            log.error(e.message, e)
        }
    }

    companion object {
        val log: Logger = LoggerFactory.getLogger(LogoutEventListener::class.java)
    }

}